package com.veeder.datacollection.rs232.comm;



import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;



import com.veeder.datacollection.config.SerialPortParam;
import com.veeder.datacollection.exception.TimeoutException;
import com.veeder.datacollection.util.GlobalConstants;



public class SerialConnection extends Connection implements
                                                SerialPortEventListener
{

   private SerialPortParam param;

   private Object serialPortLock = new Object();
   private SerialPort serialPort;

   private List<Byte> readBuffer = Collections.synchronizedList(new ArrayList<Byte>());

   private volatile boolean isValid = false;

   public SerialConnection(SerialPortParam param,
                           int connectionTimeout,
                           int readTimeout)
   {
      super(connectionTimeout, readTimeout);
      this.param = param;
   }

   public SerialPortParam getParam()
   {
      return param;
   }

   public void setParam(SerialPortParam param)
   {
      this.param = param;
   }

   @Override
   public void openConnection() throws IOException
   {
      String commName = param.getPortName();
      logger.log(Level.INFO, "open connection on port: " + commName);

      synchronized (serialPortLock)
      {
         try
         {
            CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(commName);

//            if (portIdentifier.isCurrentlyOwned())
//            {
//               logger.log(Level.WARNING, commName + " in use.");
//            }
//            else
//            {

               CommPort commPort=null;
               synchronized(SerialConnection.class){
               commPort = portIdentifier.open(this.getClass()
                                                           .getName(),
                                                       getConnectionTimeout()
                                                             * GlobalConstants.MILLISECONDS_PER_SECOND);
               logger.log(Level.INFO, "opened connection on port: " + commName);
               
               }

               if (commPort instanceof SerialPort)
               {
                  serialPort = (SerialPort) commPort;
                  serialPort.setSerialPortParams(param.getBaudRate(),
                                                 param.getDatabits(),
                                                 param.getStopbits(),
                                                 param.getParity().ordinal());
                  in = new BufferedInputStream(serialPort.getInputStream());
                  out = new BufferedOutputStream(serialPort.getOutputStream());

                  serialPort.addEventListener(this);
                  serialPort.notifyOnDataAvailable(true);
                  isValid = true;
               }
               else
               {
                  logger.log(Level.SEVERE, commName + " is not a serial port.");
               }

            }

//         }
         catch (Exception e)
         {
            logger.log(Level.SEVERE, e.toString());
            throw new IOException(e.toString());
         }

      }

   }

   @Override
   public void closeConnection()
   {
      logger.log(Level.INFO, "close connection...");
      synchronized (serialPortLock)
      {

         if (serialPort != null)
         {
            serialPort.notifyOnDataAvailable(false);
            serialPort.removeEventListener();
         }
         
         
         // didn't see the inputstream and outputstream
         // are closed when the commPort is being closed.
         // Better to close them here as well.
         if (in != null)
         {
            try
            {
               in.close();
               in = null;
            }
            catch (IOException e)
            {
               logger.log(Level.WARNING, e.toString());
            }
         }

         if (out != null)
         {
            try
            {
               out.close();
               out = null;
            }
            catch (IOException e)
            {
               logger.log(Level.WARNING, e.toString());
            }
         }
         
         if(serialPort!=null)
         {
            serialPort.close();
            logger.log(Level.INFO, "closed connection " + serialPort.getName());
            serialPort = null;
         }  
         // make sure release.
         try
         {
            Thread.sleep(300);
         }
         catch (InterruptedException e)
         {
            e.printStackTrace();
         }

 
         logger.log(Level.INFO, "realse connection resource ");
         isValid = false;
      }
   }

   @Override
   public int read(byte[] b) throws TimeoutException, IOException
   {
      synchronized (serialPortLock)
      {
         int bytesRead = -1;
         long startTime = System.currentTimeMillis();
         // when no data is read.
         while (System.currentTimeMillis() - startTime < getReadTimeout()
                                                         * GlobalConstants.MILLISECONDS_PER_SECOND
                && bytesRead < 1)
         {
            synchronized (readBuffer)
            {
               int bytesin = readBuffer.size();
               int bytesout = b.length;
               bytesRead = Math.min(bytesin, bytesout);
               for (int i = 0; i < bytesRead; i++)
               {
                  b[i] = readBuffer.get(i);
               }
               readBuffer.removeAll(readBuffer.subList(0, bytesRead));
            }

            if (bytesRead < 1)
            {
               try
               {
                  Thread.sleep(50);
               }
               catch (InterruptedException e)
               {
                  logger.log(Level.WARNING, e.toString());
               }

            }

         }

         // time exceed but still no data.
         if (bytesRead <= 0)
         {
            throw new TimeoutException("timeout in reading.");
         }

         return bytesRead;
      }
   }

   @Override
   public boolean isValid()
   {
      return isValid;
   }

   public void serialEvent(SerialPortEvent event)
   {
      switch (event.getEventType())
      {
         case SerialPortEvent.DATA_AVAILABLE:
            synchronized (readBuffer)
            {
               try
               {
                  if (isValid)
                  {
                     while (in.available() > 0)
                     {
                        int data=in.read();
                        if(data>-1){
                           readBuffer.add((byte)data);
                        }
                     }

                  }
               }
               catch (IOException e)
               {
                  logger.log(Level.SEVERE, "read error: " + e.toString());
                  closeConnection();
               }
            }
            break;

         default:
            break;
      }

   }

}
